#include "TypeChecker.h"
#include <stdio.h>
#include <assert.h>
#include "ParseTree.h"
#include "Leave.h"

//TYPE_INT,
//TYPE_INT_ARRAY,
//TYPE_ARRAY,
//TYPE_NO,
//TYPE_ERROR,
//TYPE_OP_PLUS,
//TYPE_OP_MINUS,
//TYPE_OP_MULT,
//TYPE_OP_DIV,
//TYPE_OP_LESS,
//TYPE_OP_GREATER,
//TYPE_OP_EQUAL,
//TYPE_OP_UNEQUAL,
//TYPE_OP_AND

const int DEBUG_PRINT_TRACE_FUNCTIONS = 0;

IRule* TypeChecker::rules = NULL;

void TypeChecker::setRules(IRule* rules)
{
    TypeChecker::rules = rules;
}

void TypeChecker::checkPROG(ParseTree* tree){
    if(DEBUG_PRINT_TRACE_FUNCTIONS) printf("checkPROG()\n");
    ParseTree* DECLS      = tree->getFirstChild();
    ParseTree* STATEMENTS = DECLS->getNextSibling();
    DECLS->typeCheck();
    STATEMENTS->typeCheck();
    tree->setType(TYPE_NO);
}

void TypeChecker::checkDECLS(ParseTree* tree)
{
    if(DEBUG_PRINT_TRACE_FUNCTIONS) printf("checkDECLS()\n");
    ParseTree* DECL       =      tree->getFirstChild();
    ParseTree* SEMICOLON  =      DECL->getNextSibling();
    ParseTree* DECLS      = SEMICOLON->getNextSibling();
    DECL->typeCheck();
    DECLS->typeCheck();
    tree->setType(TYPE_NO);
}

void TypeChecker::checkDECLSe(ParseTree* tree)
{
    if(DEBUG_PRINT_TRACE_FUNCTIONS) printf("checkDECLSe()\n");
    tree->setType(TYPE_NO);
}

void TypeChecker::checkDECL(ParseTree* tree)
{
    if(DEBUG_PRINT_TRACE_FUNCTIONS) printf("checkDECL()\n");
    ParseTree* integer    =           tree->getFirstChild();
    ParseTree* ARRAY      =        integer->getNextSibling();
    Leave*     identifier = (Leave*) ARRAY->getNextSibling();
    ARRAY->typeCheck();

    if(identifier->getType() != TOK_IDENTIFIER) {
        TypeChecker::rules->error("identifier already defined", identifier->getToken());
        tree->setType(TYPE_ERROR);
    } else if(ARRAY->getType() == TYPE_ERROR) { tree->setType(TYPE_ERROR);
    } else {
        tree->setType(TYPE_NO);
        if(ARRAY->getType() == TYPE_ARRAY) identifier->setType(TYPE_INT_ARRAY);
        else identifier->setType(TYPE_INT);
    }
}

void TypeChecker::checkARRAY(ParseTree* tree)
{//ARRAY ::= [ integer ]
    ParseTree* SQ_BR_OPEN  = tree->getFirstChild();//discard
    ParseTree* integer     = SQ_BR_OPEN->getNextSibling();
    ParseTree* SQ_BR_CLOSE = integer->getNextSibling();//discard

    int value = ((Leave*) integer)->getToken()->getInt();
    if(value > 0){
        tree->setType(TYPE_ARRAY);
    } else {
        TypeChecker::rules->error("no valid dimension");
        tree->setType(TYPE_ERROR);
    }
}

void TypeChecker::checkARRAYe(ParseTree* tree)
{//ARRAY ::= e
    if(DEBUG_PRINT_TRACE_FUNCTIONS) printf("checkARRAYe()\n");
    tree->setType(TYPE_NO);
}

void TypeChecker::checkSTATEMENTS(ParseTree* tree)
{//STATEMENTS ::= STATEMENT ; STATEMENTS
    ParseTree* STATEMENT  = tree->getFirstChild();
    ParseTree* semicolon  = STATEMENT->getNextSibling();//discard
    ParseTree* STATEMENTS = semicolon->getNextSibling();

    STATEMENT->typeCheck();
    STATEMENTS->typeCheck();
    tree->setType(TYPE_NO);
}

void TypeChecker::checkSTATEMENTSe(ParseTree* tree)
{//STATEMENTS ::= e
    tree->setType(TYPE_NO);
}

void TypeChecker::checkSTATEMENT_ASSIGN(ParseTree* tree)
{//STATEMENT ::= identifier INDEX = EXP
    Leave* identifier     = (Leave*) tree->getFirstChild();
    ParseTree* INDEX      =    identifier->getNextSibling();
    ParseTree* assign     =         INDEX->getNextSibling();//discard
    ParseTree* EXP        =        assign->getNextSibling();

    EXP->typeCheck();
    INDEX->typeCheck();
  
    //the following lines are the same as in: checkSTATEMENT_READ
    if(identifier->getType() == TYPE_NO){
        TypeChecker::rules->error("identifier already defined", identifier->getToken());
        tree->setType(TYPE_ERROR);
    } else if(EXP->getType() == TYPE_INT && (
        (identifier->getType() == TYPE_INT       && INDEX->getType() == TYPE_NO   ) ||
        (identifier->getType() == TYPE_INT_ARRAY && INDEX->getType() == TYPE_ARRAY))){
        tree->setType(TYPE_NO);
    } else {
        TypeChecker::rules->error("incompatible types", identifier->getToken());
        tree->setType(TYPE_ERROR);
    }
}

void TypeChecker::checkSTATEMENT_PRINT(ParseTree* tree)
{//STATEMENT ::= print ( EXP )
    ParseTree* print     =    tree->getFirstChild();//discard
    ParseTree* BR_OPEN   =   print->getNextSibling();//discard
    ParseTree* EXP       = BR_OPEN->getNextSibling();
    ParseTree* BR_CLOSE  =     EXP->getNextSibling();//discard

    EXP->typeCheck();

    tree->setType(TYPE_NO);
}

void TypeChecker::checkSTATEMENT_READ(ParseTree* tree)
{//STATEMENT ::= read( identifier INDEX)
    ParseTree* read       =             tree->getFirstChild();//discard
    ParseTree* BR_OPEN    =             read->getNextSibling();//discard
    Leave*     identifier = (Leave*) BR_OPEN->getNextSibling();//discard
    ParseTree* INDEX      =       identifier->getNextSibling();
    ParseTree* BR_CLOSE   =            INDEX->getNextSibling();//discard

    INDEX->typeCheck();

    //the following lines are the same as in: checkSTATEMENT_READ
    if(identifier->getType() == TOK_IDENTIFIER){
        TypeChecker::rules->error("identifier already defined", identifier->getToken());
        tree->setType(TYPE_ERROR);
    } else if((identifier->getType() == TYPE_INT       && INDEX->getType() == TYPE_NO   ) ||
              (identifier->getType() == TYPE_INT_ARRAY && INDEX->getType() == TYPE_ARRAY)){
        tree->setType(TYPE_NO);
    } else {
        TypeChecker::rules->error("incompatible types", identifier->getToken());
        tree->setType(TYPE_ERROR);
    }
}

void TypeChecker::checkSTATEMENT_CURLY_BR_OPEN(ParseTree* tree)
{//STATEMENT ::= { STATEMENTS }
    ParseTree* CURLY_BR_OPEN   =          tree->getFirstChild();//discard
    ParseTree* STATEMENTS      = CURLY_BR_OPEN->getNextSibling();
    ParseTree* CURLY_BR_CLOSE  =    STATEMENTS->getNextSibling();//discard

    STATEMENTS->typeCheck();
    tree->setType(TYPE_NO);
}

void TypeChecker::checkSTATEMENT_IF(ParseTree* tree)
{//STATEMENT ::= if ( EXP ) STATEMENT ELSE STATEMENT
    ParseTree* IF         =       tree->getFirstChild();//discard
    ParseTree* BR_OPEN    =         IF->getNextSibling();//discard
    ParseTree* EXP        =    BR_OPEN->getNextSibling();
    ParseTree* BR_CLOSE   =        EXP->getNextSibling();//discard
    ParseTree* STATEMENT1 =   BR_CLOSE->getNextSibling();
    ParseTree* ELSE       = STATEMENT1->getNextSibling();//discard
    ParseTree* STATEMENT2 =       ELSE->getNextSibling();

    EXP->typeCheck();
    STATEMENT1->typeCheck();
    STATEMENT2->typeCheck();

    if(EXP->getType() == TYPE_ERROR){
        tree->setType(TYPE_ERROR);
    } else { tree->setType(TYPE_NO); }
}

void TypeChecker::checkSTATEMENT_WHILE(ParseTree* tree)
{//STATEMENT ::= STATEMENT ::= while ( EXP ) STATEMENT
    ParseTree* WHILE     =     tree->getFirstChild();//discard
    ParseTree* BR_OPEN   =    WHILE->getNextSibling();//discard
    ParseTree* EXP       =  BR_OPEN->getNextSibling();
    ParseTree* BR_CLOSE  =      EXP->getNextSibling();//discard
    ParseTree* STATEMENT = BR_CLOSE->getNextSibling();

    EXP->typeCheck();
    STATEMENT->typeCheck();

    if(EXP->getType() == TYPE_ERROR){
        tree->setType(TYPE_ERROR);
    } else { tree->setType(TYPE_NO); }
}

void TypeChecker::checkEXP(ParseTree* tree)
{//EXP ::= EXP2 OP_EXP
    ParseTree* EXP2   = tree->getFirstChild();
    ParseTree* OP_EXP = EXP2->getNextSibling();

    EXP2->typeCheck();
    OP_EXP->typeCheck();

    if (OP_EXP->getType() == TYPE_NO ) tree->setType(EXP2->getType()) ;
    else if (EXP2->getType() != OP_EXP->getType()) tree->setType(TYPE_ERROR);
    else tree->setType(EXP2->getType());
}

void TypeChecker::checkEXP2_BR_OPEN(ParseTree* tree)
{//EXP2 ::= ( EXP )
    ParseTree* BR_OPEN  =    tree->getFirstChild();//discard
    ParseTree* EXP      = BR_OPEN->getNextSibling();
    ParseTree* BR_CLOSE =     EXP->getNextSibling();//discard

    EXP->typeCheck();
    tree->setType(EXP->getType());
}

void TypeChecker::checkEXP2_IDENTIFIER(ParseTree* tree)
{//EXP2 ::= identifier INDEX
    Leave* identifier = (Leave*) tree->getFirstChild();
    ParseTree* INDEX  =    identifier->getNextSibling();

    INDEX->typeCheck();

    if(identifier->getType() == TYPE_NO){
        TypeChecker::rules->error("identifier not defined", identifier->getToken());
        tree->setType(TYPE_ERROR);
    } else if(identifier->getType() == TYPE_INT && INDEX->getType() == TYPE_NO){
        tree->setType(TYPE_INT);//equals to: identifier->getType());
    } else if(identifier->getType() == TYPE_INT_ARRAY && INDEX->getType() == TYPE_ARRAY){
        tree->setType(TYPE_INT);
    } else {
        TypeChecker::rules->error("no primitive Type", identifier->getToken());
        tree->setType(TYPE_ERROR);
    }
}

void TypeChecker::checkEXP2_INTEGER(ParseTree* tree)
{//EXP2 ::= integer
    tree->setType(TYPE_INT);
}

void TypeChecker::checkEXP2_MINUS(ParseTree* tree)
{//EXP2 ::= - EXP2
    ParseTree* MINUS =  tree->getFirstChild();//discard
    ParseTree* EXP2  = MINUS->getNextSibling();

    EXP2->typeCheck();
    tree->setType(EXP2->getType());
}

void TypeChecker::checkEXP2_EXCL(ParseTree* tree)
{//EXP2 ::= ! EXP2
    ParseTree* EXCL = tree->getFirstChild();//discard
    ParseTree* EXP2 = EXCL->getNextSibling();

    EXP2->typeCheck();

    if(EXP2->getType() != TYPE_INT) tree->setType(TYPE_ERROR);
    else tree->setType(TYPE_INT);
}

void TypeChecker::checkINDEX(ParseTree* tree)
{//INDEX ::= [ EXP ]
    ParseTree* SQ_BR_OPEN  =       tree->getFirstChild();//discard
    ParseTree* EXP         = SQ_BR_OPEN->getNextSibling();
    ParseTree* SQ_BR_CLOSE =        EXP->getNextSibling();//discard

    EXP->typeCheck();
    
    if(EXP->getType() == TYPE_ERROR){
        tree->setType(TYPE_ERROR);
    } else { tree->setType(TYPE_ARRAY); }
}

void TypeChecker::checkINDEXe(ParseTree* tree)
{//INDEX ::= e
    tree->setType(TYPE_NO);
}

void TypeChecker::checkOP_EXP(ParseTree* tree)
{//OP_EXP ::= OP EXP
    ParseTree* OP  = tree->getFirstChild();
    ParseTree* EXP =   OP->getNextSibling();

    OP->typeCheck();
    EXP->typeCheck();

    tree->setType(EXP->getType());
}

void TypeChecker::checkOP_EXPe(ParseTree* tree)
{//OP_EXP ::= e
    tree->setType(TYPE_NO);
}

void TypeChecker::checkOP_PLUS   (ParseTree* tree) { tree->setType(TYPE_OP_PLUS   ); }
void TypeChecker::checkOP_MINUS  (ParseTree* tree) { tree->setType(TYPE_OP_MINUS  ); }
void TypeChecker::checkOP_MULT   (ParseTree* tree) { tree->setType(TYPE_OP_MULT   ); }
void TypeChecker::checkOP_DIV    (ParseTree* tree) { tree->setType(TYPE_OP_DIV    ); }
void TypeChecker::checkOP_LESS   (ParseTree* tree) { tree->setType(TYPE_OP_LESS   ); }
void TypeChecker::checkOP_GREATER(ParseTree* tree) { tree->setType(TYPE_OP_GREATER); }
void TypeChecker::checkOP_EQUAL  (ParseTree* tree) { tree->setType(TYPE_OP_EQUAL  ); }
void TypeChecker::checkOP_UNEQUAL(ParseTree* tree) { tree->setType(TYPE_OP_UNEQUAL); }
void TypeChecker::checkOP_AND    (ParseTree* tree) { tree->setType(TYPE_OP_AND    ); }
